package cn.yzgy.yzgyoa.common.aspect;

import cn.yzgy.yzgyoa.common.annotation.ApiDesc;
import cn.yzgy.yzgyoa.common.crsp.StatusCode;
import cn.yzgy.yzgyoa.common.entity.SysLog;
import cn.yzgy.yzgyoa.common.exception.SystemException;
import cn.yzgy.yzgyoa.common.utils.IpUtil;
import cn.yzgy.yzgyoa.common.utils.LogSensitiveInfoUtil;
import cn.yzgy.yzgyoa.ucenter.dto.LoginInfoDTO;
import cn.yzgy.yzgyoa.ucenter.entity.SysUser;
import cn.yzgy.yzgyoa.ucenter.entity.SysUserDetail;
import cn.yzgy.yzgyoa.ucenter.service.impl.SysUserDetailServiceImpl;
import cn.yzgy.yzgyoa.ucenter.service.impl.SysUserServiceImpl;
import com.alibaba.fastjson.JSON;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * <p>系统日志Aspect</p>
 * 提供在控制器方法被调用时的日志记录，
 * 记录如下信息：
 * 1.访问来源IP
 * 3.调用方法名
 * 4.方法描述（如果存在）
 * 5.参数
 * 6.执行耗时
 *
 * 可将日志记入数据库（可选）
 *
 * @author Yqsen
 * @version 0.0.1
 * @since 2019年8月9日14:47:04
 */

@Slf4j
@Aspect
@Order(99)
@Component
public class LogAspect {

    private final static String HTTP_GET_METHOD = "GET";
    private final static String ANNO_USER_NAME = "匿名用户";
    private final static String LOGIN_URL = "ucenter/login";

    private final SysUserDetailServiceImpl sysUserDetailService;
    private final SysUserServiceImpl sysUserService;
    private final LogSensitiveInfoUtil logSensitiveInfoUtil;

    public LogAspect(SysUserDetailServiceImpl sysUserDetailService, SysUserServiceImpl sysUserService, LogSensitiveInfoUtil logSensitiveInfoUtil) {
        this.sysUserDetailService = sysUserDetailService;
        this.sysUserService = sysUserService;
        this.logSensitiveInfoUtil = logSensitiveInfoUtil;
    }

    @Pointcut("execution(public * *(..)) && within(@cn.yzgy.yzgyoa.common.annotation.Syslog *)")
    public void pointCut() {}

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        SysLog syslog = getLog(proceedingJoinPoint);
        HttpServletRequest request = syslog.getRequest();
        log.info("USER:[{}] {} [{}] from [{}] : {} Args：{}. "
                ,syslog.getUserName(), request.getMethod(),request.getRequestURI(), syslog.getIp(), syslog.getMethod(), syslog.getRequestArgs());
        long startTime = System.currentTimeMillis();
        Object res = proceedingJoinPoint.proceed();
        String responseData = JSON.toJSONString(res);
        long timeCast = System.currentTimeMillis() - startTime;

        //设置响应消息 成功标志 响应时间 执行耗时
        syslog.setResponse(responseData)
                .setSuccessFlag(true)
                .setResponseTime(new Date())
                .setTimeCast(timeCast);
        log.info("USER:[{}] {} deal in {}ms",
                syslog.getUserName(),syslog.getMethod(),timeCast);
        logSensitiveInfoUtil.insert(syslog);
        return res;
    }

    @AfterThrowing(pointcut = "pointCut()", throwing = "e", argNames = "joinPoint,e")
    public void exceptionHandler(JoinPoint joinPoint, Exception e) {
        final String exceptionMsgTemplate = "Exception:%s\nMessage:%s";
        SysLog sysLog = getLog(joinPoint);
        log.error("USER:[{}] {} throw Exceptions:{}", sysLog.getUserName(), sysLog.getMethod(), e.getMessage());
        sysLog.setResponse("{}")
                .setSuccessFlag(false)
                .setResponseTime(new Date())
                .setTimeCast(0L)
                .setExceptionData(String.format(exceptionMsgTemplate,e.toString(),e.getMessage()));
        if (e instanceof SystemException) {
            sysLog.setExceptionData(
                    String.format(
                            exceptionMsgTemplate, ((SystemException) e).getStatusCode().getMsg(),
                            ((SystemException) e).getAttachStr()));
        }
        logSensitiveInfoUtil.insert(sysLog);
    }

    /**
     * 根据不同情况获取用户对象
     * 1. Portal接口
     * 2. 登陆接口
     * 3. 其他情况
     *
     * @return 该情况实用的用户对象
     */
    private SysUser getUserObj(HttpServletRequest request, Object[] requestArgs) {
        final String iotPortalUri = "iot/portal";
        String uri = request.getRequestURI();
        if(uri.contains(iotPortalUri)){
            return new SysUser().setId(0L);
        }else if (uri.contains(LOGIN_URL)) {
            LoginInfoDTO dto = (LoginInfoDTO) requestArgs[0];
            assert dto != null;
            return sysUserService.getUserByUserName(dto.getUserName());
        }else{
            SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();
            if (user == null) {
                throw new SystemException(StatusCode.UN_AUTHORIZATION);
            }
            return user;
        }
    }

    /**
     * 生成日志记录对象
     *
     * @param joinPoint 切入点
     * @return 日志对象
     */
    private SysLog getLog(JoinPoint joinPoint) {
        SysLog sysLog = new SysLog();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        // 获取方法上标注的名字、参数及参数名
        ApiDesc apiDesc = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ApiDesc.class);

        // 获取API描述
        sysLog.setMethod(String.format("%s(%s)", apiDesc == null ? "未命名API" : apiDesc.value(), ((MethodSignature) joinPoint.getSignature()).getMethod().getName()));

        // 获取URI
        sysLog.setUrl(httpServletRequest.getRequestURI());

        //获取IP地址
        sysLog.setIp(IpUtil.getIpAddr(httpServletRequest));

        // 获取浏览器及操作系统信息
        UserAgent userAgent = UserAgent.parseUserAgentString(httpServletRequest.getHeader("User-Agent"));
        sysLog.setBrowser(String.format("%s %s", userAgent.getBrowser().toString(), userAgent.getBrowserVersion()))
                .setOsInfo(userAgent.getOperatingSystem().toString());

        // 获取操作用户信息
        Object[] args = joinPoint.getArgs();
        SysUser user = getUserObj(httpServletRequest, args);
        SysUserDetail sysUserDetail;
        if (null == user) {
            if (httpServletRequest.getRequestURI().contains(LOGIN_URL)) {
                LoginInfoDTO dto = (LoginInfoDTO) joinPoint.getArgs()[0];
                assert dto != null;
                sysUserDetail = new SysUserDetail().setName(dto.getUserName());
            }else {
                sysUserDetail = new SysUserDetail().setName(ANNO_USER_NAME);
            }
            user = new SysUser().setId(999999999999L);
        }else {
            sysUserDetail = sysUserDetailService.getById(user.getId());
        }
        sysLog.setUserId(user.getId().toString())
                .setUserName(sysUserDetail.getName());

        // GET方法 单独处理参数组装方式
        if (HTTP_GET_METHOD.equals(httpServletRequest.getMethod())) {
            // 获取请求参数 body及parameters
            String[] argsName = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
            Map<String, String> parameters = new HashMap<>(args.length);
            for (int i = 0; i < args.length; i++) {
                parameters.put(argsName[i], args[i] == null ? "" : args[i].toString());
            }
            sysLog.setRequestArgs(JSON.toJSONString(parameters));
        } else {
            String resT;
            if (args.length > 0 && args[0] instanceof MultipartFile) {
                resT = "{\"fileUpload\":\"二进制文件\"}";
            } else if (args.length > 0){
                resT = JSON.toJSONString(args[0]);
            } else {
                resT = "{}";
            }
            sysLog.setRequestArgs(resT);
        }
        sysLog.setNeedLog(apiDesc == null || apiDesc.log());
        return sysLog.setRequestTime(new Date()).setRequest(httpServletRequest);
    }
}
